package com.zhao.dota.service.impl;

import com.zhao.dota.contant.CacheConstant;
import com.zhao.dota.contant.TeamConst;
import com.zhao.dota.model.*;
import com.zhao.dota.service.*;
import com.zhao.dota.vo.*;
import org.springframework.cache.annotation.CacheConfig;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.cache.annotation.Caching;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.time.YearMonth;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;

@Service
@CacheConfig(cacheNames = CacheConstant.REPORT)
public class ReportServiceImpl implements ReportService {

    @Resource
    private PlayerService playerService;
    @Resource
    private MatchPlayerService matchPlayerService;
    @Resource
    private HeroService heroService;
    @Resource
    private PerformanceService performanceService;

    @Override
    @Cacheable(key = "'month:'.concat(#yearMonth.toString())", unless = "#result == null")
    public Statistical generateMonthlyReport(YearMonth yearMonth, List<DotaMatch> matchList) {
        Statistical statistical = new Statistical();
        statistical.setYearMonth(yearMonth);
        statistical.setCount(matchList.size());

        Map<Integer, Hero> heroMap = new HashMap<>();
        Map<Integer, Player> playerMap = new HashMap<>();

        Map<Hero, Integer> heroEnterMap = new HashMap<>();
        Map<Player, Integer> playerEnterMap = new HashMap<>();

        int maxSingleMatchKill = 0;
        int maxSingleMatchDeath = 0;
        List<PlayerData<Integer>> maxKills = new ArrayList<>();
        List<PlayerData<Integer>> maxDeaths = new ArrayList<>();

        Map<Integer, TotalsVo> totalsMap = new HashMap<>();
        Map<Integer, Set<HeroVo>> heroShowUpMap = new HashMap<>();
        Set<HeroVo> heroSet = new HashSet<>();
        Set<MatchPlayerVo> matchPlayerVoSet = new HashSet<>();
        for (DotaMatch dotaMatch : matchList) {
            // 统计天辉和夜宴胜场
            boolean radiantWin = dotaMatch.getWinner() == TeamConst.RADIANT;
            if (radiantWin) {
                statistical.setRadiantWin(statistical.getRadiantWin() + 1);
            } else {
                statistical.setDireWin(statistical.getDireWin() + 1);
            }
            // 统计本场出场玩家
            List<MatchPlayer> matchPlayers = matchPlayerService.findByMatchId(dotaMatch.getId());
            Map<Integer, MatchPlayer> matchPlayerMap = matchPlayers.stream().collect(Collectors.toMap(MatchPlayer::getId, Function.identity()));
            List<Integer> playerIds = matchPlayers.stream().map(MatchPlayer::getPlayerId)
                    .filter(id -> !playerMap.containsKey(id)).collect(Collectors.toList());
            if (playerIds.size() > 0) {
                List<Player> players = playerService.listByIds(playerIds);
                for (Player player :players) {
                    playerMap.put(player.getId(), player);
                }
            }
            // 本场英雄
            List<Integer> heroIds = matchPlayers.stream().map(MatchPlayer::getHeroId)
                    .filter(id -> !heroMap.containsKey(id)).collect(Collectors.toList());
            if (!heroIds.isEmpty()) {
                List<Hero> heroes = heroService.listByIds(heroIds);
                for (Hero hero : heroes) {
                    heroMap.put(hero.getId(), hero);
                }
            }

            for (MatchPlayer matchPlayer : matchPlayers) {
                // 保存本场英雄和玩家
                Player player = playerMap.get(matchPlayer.getPlayerId());
                if (player == null) {
                    continue;
                }
                Hero hero = heroMap.get(matchPlayer.getHeroId());
                // 计算英雄出场次数
                heroEnterMap.compute(hero, (k, v) -> {
                    if (v == null) {
                        return 1;
                    }
                    return v + 1;
                });
                // 计算玩家出场次数
                playerEnterMap.compute(player, (k, v) -> {
                    if (v == null) {
                        return 1;
                    }
                    return v + 1;
                });
                Set<HeroVo> heros = heroShowUpMap.computeIfAbsent(player.getId(), k -> new HashSet<>());
                HeroVo heroVo = new HeroVo(hero);
                heros.add(heroVo);
                heroSet.add(heroVo);
                matchPlayerVoSet.add(new MatchPlayerVo(matchPlayer));
            }
            // 比赛详情
            List<Performance> performances = performanceService.findByMatchId(dotaMatch.getId());
            for (Performance performance : performances) {
                MatchPlayer matchPlayer = matchPlayerMap.get(performance.getMatchPlayerId());
                if (performance.getKills() >= maxSingleMatchKill) {
                    if (performance.getKills() > maxSingleMatchKill) {
                        maxSingleMatchKill = performance.getKills();
                        maxKills.clear();
                    }
                    PlayerData<Integer> playerData = new PlayerData<>();
                    playerData.setPlayerId(matchPlayer.getPlayerId());
                    playerData.setData(maxSingleMatchKill);
                    playerData.setMatchPlayerId(matchPlayer.getId());
                    playerData.setHeroId(matchPlayer.getHeroId());
                    playerData.setMatchId(dotaMatch.getId());
                    maxKills.add(playerData);
                }
                if (performance.getDeaths() >= maxSingleMatchDeath) {
                    if (performance.getDeaths() > maxSingleMatchKill) {
                        maxSingleMatchDeath = performance.getDeaths();
                        maxDeaths.clear();
                    }
                    PlayerData<Integer> playerData = new PlayerData<>();
                    playerData.setPlayerId(matchPlayer.getPlayerId());
                    playerData.setData(maxSingleMatchDeath);
                    playerData.setMatchPlayerId(matchPlayer.getId());
                    playerData.setHeroId(matchPlayer.getHeroId());
                    playerData.setMatchId(dotaMatch.getId());
                    maxDeaths.add(playerData);
                }

                Player player = playerMap.get(matchPlayer.getPlayerId());
                if (player == null) {
                    continue;
                }
                TotalsVo totals = totalsMap.computeIfAbsent(player.getId(), k -> new TotalsVo(true));
                totals.setDeaths(totals.getDeaths() + performance.getDeaths());
                totals.setKills(totals.getKills() + performance.getKills());
                totals.setAssists(totals.getAssists() + performance.getAssists());
                totals.setTotalEarnedGold(totals.getTotalEarnedGold() + performance.getTotalEarnedGold());
                totals.setLastHitCount(totals.getLastHitCount() + performance.getLastHitCount());
                totals.setDenyCount(totals.getDenyCount() + performance.getDenyCount());
                totals.setHeroDamage(totals.getHeroDamage() + performance.getHeroDamage());
                totals.setTotalEarnedXp(totals.getTotalEarnedXp() + performance.getTotalEarnedXp());
                totals.setFirstBloodClaimed(totals.getFirstBloodClaimed() + performance.getFirstBloodClaimed());
                totals.setFirstBloodGiven(totals.getFirstBloodGiven() + performance.getFirstBloodGiven());
                totals.setTotalMatches(totals.getTotalMatches() + 1);
                totals.setTotalTimes(totals.getTotalTimes() + dotaMatch.getGameTime());
                totals.setWins(totals.getWins() + matchPlayer.getWin());
                totals.setTowerDamage(totals.getTowerDamage() + performance.getTowerDamage());
                totals.setHealing(totals.getHealing() + performance.getHealing());
                totals.setXpm(totals.getXpm() + performance.getXpm());
                totals.setGpm(totals.getGpm() + performance.getGpm());
                totals.setPlayerId(player.getId());
                totals.setStuns(performance.getStuns() + totals.getStuns());
                totals.setCreepsStacked(performance.getCreepsStacked() + totals.getCreepsStacked());
            }
        }
//        for (TotalsVo totalsVo : totalsMap.values()) {
//            totalsVo.setXpm(totalsVo.getXpm() / totalsVo.getTotalMatches());
//            totalsVo.setGpm(totalsVo.getGpm() / totalsVo.getTotalMatches());
//        }

        statistical.setHeroCount(heroMap.size());
        statistical.setMaxSingleMatchKill(maxKills);
        statistical.setMaxSingleMatchDeath(maxDeaths);
        statistical.setTotals(totalsMap);
        statistical.setPlayerMap(playerMap);
        statistical.setPlayerHeroMap(heroShowUpMap);
        statistical.setHeroMap(heroSet.stream().collect(Collectors.toMap(Hero::getId, Function.identity())));
        statistical.setMatchPlayerMap(matchPlayerVoSet.stream().collect(Collectors.toMap(MatchPlayerVo::getId, Function.identity())));
        statistical.setMatchMap(matchList.stream().collect(Collectors.toMap(DotaMatch::getId, Function.identity())));

        for (Map.Entry<Integer, TotalsVo> e : totalsMap.entrySet()) {
            Integer key = e.getKey();
            Player player = playerMap.get(key);
            TotalsVo value = e.getValue();
            System.out.printf("[%s] 共参加了[%d]场比赛，获胜[%d]场，胜率为[%.2f%s] %n", player.getName(), value.getTotalMatches(),
                    value.getWins(), value.getWins() * 1.0d / value.getTotalMatches(), "%");
        }
        return statistical;
    }

    @Override
    @CacheEvict(key = "'month:'.concat(#yearMonth.toString())")
    public void clearCache(YearMonth yearMonth) {

    }

}
